Rodrick

vuePress-theme-reco Rodrick    2022
Rodrick Rodrick

Choose mode

  • dark
  • auto
  • light
Home
Category
  • CS基础
  • 数据库
  • 前端
  • 其他
Tag
About
Timeline
D&T
  • 官方文档

    • Vue
    • Vue3
    • Webpack
    • MDN
    • Node中文网
    • React
    • 小程序
    • FineReport
  • 学习面试

    • 现代JavaScript教程
    • ES6
    • 阿西河
    • LeetCode
    • 牛客网
  • 工具

    • bejson
Contact
  • Github
  • Gitee
author-avatar

Rodrick

62

Article

18

Tag

Home
Category
  • CS基础
  • 数据库
  • 前端
  • 其他
Tag
About
Timeline
D&T
  • 官方文档

    • Vue
    • Vue3
    • Webpack
    • MDN
    • Node中文网
    • React
    • 小程序
    • FineReport
  • 学习面试

    • 现代JavaScript教程
    • ES6
    • 阿西河
    • LeetCode
    • 牛客网
  • 工具

    • bejson
Contact
  • Github
  • Gitee
  • JS

    • ES6核心语法
    • 模块化&Webpack
    • Promise&异步函数async
    • WebSocket原理浅析
    • axios的使用
    • ES5的变量提升和ES6的暂时性死区
    • call、apply、bind的用法
    • 对象的原始值转换
    • 可选链"?."
    • 随机数方法
    • 深浅拷贝实现
    • Array常用处理
    • 防抖与节流
    • postMessage窗口间通讯
    • 解构赋值
    • Map&Set
    • 日期和时间
    • 对象属性
    • Toast组件简单封装
    • 原型链
    • 类 Class
    • Generator与异步迭代
    • 偏函数Partial和柯里化Currying
    • DOM操作
    • 事件处理
    • 事件循环EventLoop
    • 网络请求
    • 浏览器中存储数据
    • 正则表达式
  • CSS

  • 其他

类 Class

vuePress-theme-reco Rodrick    2022

类 Class

Rodrick 2020-11-30 jsES6

# 基本语法

class MyClass {
  prop = value; // 属性

  constructor(...) { // 构造器
    // ...
  }

  method(...) {} // method

  get something(...) {} // getter 方法
  set something(...) {} // setter 方法

  [Symbol.iterator]() {} // 有计算名称(computed name)的方法(此处为 symbol)
  // ...
}

let xx = new MyClass(...)

# 继承 extends

# 基本语法

class Animal{
	constructor(name){
  	this.name=name
  }
}

class Rabbit extends Animal{
	say(){
		alert(`my name is ${this.name}`)
	}
}

let r1 = new Rabbit("r1")
r1.say() // my name is r1

# super 关键字

super 关键字可以做两件事:

  • super.method(...) 可以调用父类的方法
class Animal {
  constructor(name) {
    this.name = name
  }

  run() {
    alert(`${this.name} run!`)
  }
}

class Rabbit extends Animal {
  say() {
    alert(`my name is ${this.name}`)
  }

  laterRun() {
    // 调用父类的方法run(),箭头函数中可以正常调用super
    setTimeout(() => super.run(), 1000)
  }

}

let r1 = new Rabbit("r1")
r1.laterRun() // 1s后 "r1 run!"
  • super(...) 可以调用父类的 constructor

刚才我们继承后的类,都没有自己的 constructor ,如果我们不写集成类自己的 constructor ,其实他自己会生成一个这样的空构造器:

class Rabbit extends Animal {
  // 为没有自己的 constructor 的扩展类生成的
  constructor(...args) {
    super(...args);
  }
}

注意:继承类的 constructor 必须调用 super(...),并且 (!) 一定要在使用 this 之前调用。

class Animal {
  constructor(name) {
    this.name = name
  }

  run() {
    alert(`${this.name} run!`)
  }
}

class Rabbit extends Animal {
  constructor(name,age){
  	super(name)
    this.age = age
  }
  	
  sayInfo(){
		alert(`${this.name}'s age is ${this.age}`)
	}

}

let r1 = new Rabbit("r1",18)
r1.sayInfo() // r1's age is 18

# 重写

class Animal {
  name = 'animal';

  constructor() {
    alert(this.name); // (*)
  }
}

class Rabbit extends Animal {
  name = 'rabbit';
}

new Animal(); // animal
new Rabbit(); // animal

这里 Rabbit 直接在自己的类里定义了自己的 name,但是 new Rabbit 还是打印 animal ,显然不是我们想要的结果
再看看下面这个:

class Animal {
  showName() {  // 而不是 this.name = 'animal'
    alert('animal');
  }

  constructor() {
    this.showName(); // 而不是 alert(this.name);
  }
}

class Rabbit extends Animal {
  showName() {
    alert('rabbit');
  }
}

new Animal(); // animal
new Rabbit(); // rabbit

重写的 showName 方法却按照我们预想的结果正确执行了,这是为什么?

实际上,原因在于字段初始化的顺序。类字段是这样初始化的:

  • 对于基类(还未继承任何东西的那种),在构造函数调用前初始化。
  • 对于派生类,在 super() 后立刻初始化。


接下来我标注一下两个情况下 new Rabbit() 的执行顺序:

  • 字段重写
class Animal {
  name = 'animal'; // {3} 对于基类(还未继承任何东西的那种),在构造函数调用前初始化

  constructor() {
    // {4} 对于派生类,在 super() 后立刻初始化。this 虽然是 Rabbit ,但是此时 Rabbit 的 name 还是从父类继承来的 "animal"
    alert(this.name); 
  }
}

class Rabbit extends Animal { // {2}
  name = 'rabbit'; // {5} 这个是会执行的
}

new Rabbit(); // animal  // {1}
  • 方法重写
class Animal {
  showName() {  // 方法不是字段,不会在 constructor 之前被初始化,在这个情况下更不会被执行
    alert('animal');
  }

  constructor() {
    this.showName(); // 而不是 alert(this.name); // {3}
  }
}

class Rabbit extends Animal { // {2}
  showName() {
    alert('rabbit'); // {4}
  }
}

new Rabbit(); // rabbit // {1}

# [[HomeObject]]

为了提供解决方法,JavaScript 为函数添加了一个特殊的内部属性:[[HomeObject]]。
当一个函数被定义为类或者对象方法时,它的 [[HomeObject]] 属性就成为了该对象。
然后 super 使用它来解析(resolve)父原型及其方法。

let animal = {
  name: "Animal",
  eat() {         // animal.eat.[[HomeObject]] == animal
    alert(`${this.name} eats.`);
  }
};

let rabbit = {
  __proto__: animal,
  name: "Rabbit",
  eat() {         // rabbit.eat.[[HomeObject]] == rabbit
    super.eat();
  }
};

将一个带有 super 的方法从一个对象复制到另一个对象是不安全的 而 [[HomeObject]] 是仅仅被作用于 super 对象的。
**

let animal = {
  sayHi() {
    alert(`I'm an animal`);
  }
};

// rabbit 继承自 animal
let rabbit = {
  __proto__: animal,
  sayHi() {
    super.sayHi(); // [[HomeObject]] 是 rabbit
  }
};

let plant = {
  sayHi() {
    alert("I'm a plant");
  }
};

// tree 继承自 plant
let tree = {
  __proto__: plant,
  sayHi: rabbit.sayHi // (*) 它的 [[HomeObject]] 是 rabbit,因为它是在 rabbit 中创建的。所以 super 找的对象还是 animal
};

tree.sayHi();  // I'm an animal 

# 继承于 Function 还是 Object?

  • 我们一般声明 class Rabbit 的时候, Rabbit.__proto__ === Function.prototype
  • 但是声明 class Rabbit extends Object 的时候,首先我们需要在 constructor 里调用 super() 其次 Rabbit.__proto__ === Object


这个会影响到我们是否能使用一些 Function 的内在方法,了解就好

# 静态 static

# 概念

我们可以把一个方法赋值给类的函数本身,而不是赋给它的 "prototype"。这样的方法被称为 静态的(static)。
设置为静态的属性或者方法是可被继承的,他们是类本身的,而不是给实例用的。

# 静态属性

class Article {
  static publisher = "AA";
}

alert( Article.publisher ); // AA

// 相当于
Article.publisher = "AA";

# 静态方法

class Article {
  constructor(title, date) {
    this.title = title;
    this.date = date;
  }

  static createTodays() {
    // 记住 this = Article
    return new this("Today's digest", new Date());
  }
}

let article = Article.createTodays();

alert( article.title ); // Today's digest

我们经常使用的 Object.xxx() 也就是 Object 类里的静态方法

# 私有和受保护的属性和方法

# 约定上的受保护写法 "_xxx"

受保护的属性通常以下划线 _ 作为前缀。
这不是在语言级别强制实施的,但是程序员之间有一个众所周知的约定,即不应该从外部访问此类型的属性和方法。
所以我们的属性将被命名为 _waterAmount:

class CoffeeMachine {
  _waterAmount = 0;

  set waterAmount(value) {
    if (value < 0) throw new Error("Negative water");
    this._waterAmount = value;
  }

  get waterAmount() {
    return this._waterAmount;
  }

  constructor(power) {
    this._power = power;
  }

}

// 创建咖啡机
let coffeeMachine = new CoffeeMachine(100);

// 加水
coffeeMachine.waterAmount = -10; // Error: Negative water

# 语法上的私有 "#xxx"

这儿有一个马上就会被加到规范中的已完成的 Javascript 提案(实际上chrome等一些浏览器已经可以识别),它为私有属性和方法提供语言级支持。
私有属性和方法应该以 # 开头。它们只在类的内部可被访问。

class CoffeeMachine {
  #waterLimit = 200;

  #checkWater(value) {
    if (value < 0) throw new Error("Negative water");
    if (value > this.#waterLimit) throw new Error("Too much water");
  }

}

let coffeeMachine = new CoffeeMachine();

// 不能从类的外部访问类的私有属性和方法
coffeeMachine.#checkWater(); // SyntaxError: Private field '#checkWater' must be declared in an enclosing class
coffeeMachine.#waterLimit = 1000; // SyntaxError: Private field '#waterLimit' must be declared in an enclosing class

但是注意, #xxx 的属性和方法,被继承的 class 并不能直接访问他们,想要访问的话,只能去访问 get 或者 set 方法。
所以规定下还是比较建议使用第一种写法。

# 类的检查[扩展类型判断]

# instanceof 用法

obj instanceof Class

如果 obj 隶属于 Class 类(或 Class 类的衍生类),则返回 true。

class Rabbit {}
let rabbit = new Rabbit();

// rabbit 是 Rabbit class 的对象吗?
alert( rabbit instanceof Rabbit ); // true

# Symbol.hasInstance

obj instanceof Class 算法的执行过程大致如下:

  1. 如果 Class 有静态方法 Symbol.hasInstance,那就直接调用这个方法
// 设置 instanceOf 检查
// 并假设具有 canEat 属性的都是 animal
class Animal {
  static [Symbol.hasInstance](obj) {
    if (obj.canEat) return true;
  }
}

let obj = { canEat: true };

alert(obj instanceof Animal); // true:Animal[Symbol.hasInstance](obj) 被调用
  1. 没有的话就按照原型链上去查找

这里还要提到一个方法 objA.isPrototypeOf(objB) ,如果 objA 处在 objB 的原型链中,则返回 true。

# Object.prototype.toString(...)

  • 对于 number 类型,结果是 [object Number]
  • 对于 boolean 类型,结果是 [object Boolean]
  • 对于 null:[object Null]
  • 对于 undefined:[object Undefined]
  • 对于数组:[object Array]

欢迎来到 Rodrick
看板娘